package com.learning.mybatis.plugin.application.plugin;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.util.Properties;

/**
 * ClassName: CustomPlugin
 * Description: 插件实例
 * <p>
 * 1. 插件开发过程步骤如下：
 * 1.1 确定需要拦截的签名：对象、方法、参数：@Intercepts、@Signature 注解
 * 1.2 实现拦截方法：实现接口，重写方法
 * 1.3 配置与运行：需要在配置文件中配置
 * <p>
 * 2. 实例：限制数据库查询的数据量 QueryLimitPlugin
 * Date: 2020/8/14 15:20 【需求编号】
 *
 * @author Sam Sho
 * @version V1.0.0
 */
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class QueryLimitPlugin implements Interceptor {

    /**
     * 默认限制查询返回行数
     */
    private int limit;

    /**
     * 1. 直接覆盖所拦截对象原有的方法，因此他是核心方法。通过 invocation 对象可以反射调度原来对象的方法
     * 2. 使用 MetaObject 工具类，有效读取或修改重要对象的属性
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取到拦截对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 需要使用 MetaObject 类处理
        // 【重要】MetaObject 是 MyBatis 框架设计用它来有效读取或修改重要对象的属性。
        // 在 MyBatis 对象中大量使用这个类进行包装，包括四大对象，可以通过他来给四大对象的某些属性赋值
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        sql = sql + "  limit   " + limit;

        // getValue 方法用于获取对象属性值，支持OGNL
        // [分析 delegate.boundSql.sql ]:
        // 拦截到的 StatementHandler 实际上是 RoutingStatementHandler 类，有一个属性 delegate 代表实际的 StatementHandler对象，真实的对象内部有一个属性 BoundSql，该属性对象又有一个属性 sql 真正存放执行的语句
        metaObject.getValue("delegate.boundSql.sql");
        // setValue 方法用于修改对象属性值，支持OGNL
        metaObject.setValue("delegate.boundSql.sql", sql);

        return invocation.proceed();
    }

    /**
     * 1. target 是拦截对象，他的作用是给被拦截对象生成一个代理对象，并返回。（构建4大对象时，调用）
     * 2. MyBatis 专门有一个工具类 Plugin ，用于这边使用生成代理对象，即四大对象实际是 Plugin 对象的代理。
     * 3. 访问四大对象的方法，需要访问这个代理对象Plugin的 invoke 方法。在这个方法中，实现额前置调用插件拦截方法 Interceptor.intercept()
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 1. 允许在插件元素中配置所需参数，方法在插件初始化的时候被调用一次，然后把插件对象存入配置中
     * 2. 在配置文件装配时被调用
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        String limit = properties.getProperty("limit");
        this.limit = Integer.parseInt(limit);
    }
}
